package nayasis.common.db.manager.connection;

import java.sql.Connection;
import java.util.Vector;

import nayasis.common.db.manager.etc.DriverManager;
import nayasis.common.exception.unchecked.DatabaseException;

/**
 * 커넥션 Pool 객체
 * 
 * @author nayasis
 *
 */
public class ConnectionPool {

	private final int TIMEOUT = 300;

	/** Connection Pool 이름 */
	private String connectionPoolName = "";
	
	/** DB ID */
	private String id;

	/** DB Password */
	private String password;
	
	/** DB 접속 URL */
	private String url;
	
	/** 최대 Connection 갯수 */
	private int maxConnectionCount  = 0;
	
	/** Connection을 얻기 위한 최대 대기시간 (second) */
	private int maxWaitSecond = 3000;
	
	/** 대기중인 Connection들이 모여있는 공간 **/
	private Vector<WrapperConnection> idleConnections = new Vector<>();
	
	/** 사용중인 Connection들이 모여있는 공간 **/
	private Vector<WrapperConnection> activeConnections = new Vector<>();

	
	/**
	 * 기본 생성자
	 * 
	 * @param connectionPoolName  Connection Pool 이름
	 * @param url                 DB 접속 URL
	 * @param id                  DB 사용자 ID
	 * @param password            DB 사용자 패스워드
	 * @param maxConnectionCount  최대 Connection 갯수
	 * @param initConnectionCount 초기 Connection 갯수
	 */
	public ConnectionPool( String connectionPoolName, String url, String id, String password, int maxConnectionCount, int initConnectionCount ) {

		this.connectionPoolName  = connectionPoolName;
		this.url                 = url;
		this.id                  = id;
		this.password            = password;
		this.maxConnectionCount  = maxConnectionCount;
	
		for( int i = 0; i < initConnectionCount; i++ ) {
			idleConnections.add( createConnection() );
		}
		
	}

	/**
	 * 기본생성자
	 * 
	 * @param connectionPoolName  Connection Pool 이름
	 * @param url                 DB 접속 URL
	 * @param id                  DB 사용자 ID
	 * @param password            DB 사용자 패스워드
	 */
	public ConnectionPool( String connectionPoolName, String url, String id, String password ) {
		this( connectionPoolName, url, id, password, 2, 0 );
	}
	
	/**
	 * Pool에 커넥션을 반납한다.
	 * 
	 * @param connection DB 커넥션
	 */
	public synchronized void closeConnection( WrapperConnection connection ) {
		
		if( connection == null ) return;
		
		connection.commit();
		
		activeConnections.remove( connection );
		
		idleConnections.add( connection );
		
		notifyAll();
		
	}

	/**
	 * 연결되어있는 모든 Connection을 닫는다.
	 */
	public synchronized void clear() {
		
		for( WrapperConnection conn : idleConnections ) {
			conn.close();
		}
		
		idleConnections.clear();
		
		for( WrapperConnection conn : activeConnections ) {
			conn.close();
		}
		
		activeConnections.clear();
		
	}
	
	/**
	 * Pool에서 DB 커넥션을 가져온다.
	 * 
	 * @return DB 커넥션
	 */
	public synchronized WrapperConnection getConnection() {
		
		WrapperConnection conn = null;
		
		// 대기중인 Connection이 있다면 이를 건네준다.
		if( idleConnections.size() > 0 ) {
			
			conn = idleConnections.firstElement();
			
			idleConnections.remove( conn );
			
			if( ! conn.isValid() ) {
				conn.close();
				conn = getConnection();
			} else {
				activeConnections.add( conn );
			}

		// 아직 최대 Connection 을 사용하지 않고 있다면, Connection을 새로 만들어 건네준다.
		} else if( isUnderMaxConnectionCount() ) {
			
			conn = createConnection();
			
			activeConnections.add( conn );

		// 최대 Connection을 사용중이라면 자원이 사용가능한 상태가 될 때까지 기다린다.
		} else {

			long startTime = System.nanoTime();
			
			while( (conn = getConnection()) == null ) {
				try {
					wait( TIMEOUT );
				} catch ( InterruptedException e ) {
					
					if( System.nanoTime() - startTime > maxWaitSecond * 1_000_000 ) {
						throw new DatabaseException( e, "Time to wait for getting connection is over (@ second)", maxWaitSecond );
					}
					
				}
			}

		}
		
		return conn;
		
	}
	
	/**
	 * 사용중인 Connection 갯수가 최대갯수 미만인지 여부를 확인한다.
	 * 
	 * @return 최대갯수 미만여부
	 */
	private boolean isUnderMaxConnectionCount() {
		
		if( maxConnectionCount <= 0 ) return true;
		
		int totalConnectionCount = idleConnections.size() + activeConnections.size();
		
		return ( totalConnectionCount < maxConnectionCount );

	}
	
	/**
	 * 커넥션을 신규로 생성한다.
	 * 
	 * @return DB 커넥션
	 */
	private WrapperConnection createConnection() {
		
		Connection conn = null;
		
		if( this.id == null ) {
			conn = DriverManager.getConnection( this.url );
		} else {
			conn = DriverManager.getConnection( this.url, this.id, this.password );
			
		}
		
		return new WrapperConnection( conn, connectionPoolName );
		
	}
	
}
